package nachos.threads;

import nachos.machine.*;

import java.util.ArrayList;

/**
 * An implementation of condition variables that disables interrupt()s for
 * synchronization.
 *
 * <p>
 * You must implement this.
 *
 * @see	nachos.threads.Condition
 */
public class Condition2 {
    /**
     * Allocate a new condition variable.
     *
     * @param	conditionLock	the lock associated with this condition
     *				variable. The current thread must hold this
     *				lock whenever it uses <tt>sleep()</tt>,
     *				<tt>wake()</tt>, or <tt>wakeAll()</tt>.
     */
    public Condition2(Lock conditionLock) {
	this.conditionLock = conditionLock;
    }

    /**
     * Atomically release the associated lock and go to sleep on this condition
     * variable until another thread wakes it using <tt>wake()</tt>. The
     * current thread must hold the associated lock. The thread will
     * automatically reacquire the lock before <tt>sleep()</tt> returns.
     */
    public void sleep() {

        /* If the current thread doesn't hold the lock, then abort */
        Lib.assertTrue(conditionLock.isHeldByCurrentThread());

        // Don't want to be interrupted while setting something important
        boolean intStatus = Machine.interrupt().disable();

        Lib.debug(dbgCondition, "Sleeping Thread: " + KThread.currentThread().toString());

        // Release the lock for now
        conditionLock.release();

        // Put the sleeping thread in the queue
        sleepingQueue.add( KThread.currentThread() );

        // Put this thread to sleep so it blocks
        KThread.currentThread().sleep();

        // When you wake up again, reaquire the lock
        conditionLock.acquire();

        // Okay, should be safe now, restore interrupts
        Machine.interrupt().restore(intStatus);

    }

    /**
     * Wake up at most one thread sleeping on this condition variable. The
     * current thread must hold the associated lock.
     */
    public void wake() {
	Lib.assertTrue(conditionLock.isHeldByCurrentThread());

        // Don't want to be interrupted while setting something important
        boolean intStatus = Machine.interrupt().disable();

        // If there are any threads sleeping
        if ( sleepingQueue.size() > 0 ) {
            Lib.debug(dbgCondition, "Waking Thread: " + sleepingQueue.get( 0 ).toString());
            // Wake up the thread
            sleepingQueue.get( 0 ).ready();
            // Remove the thread from the list
            sleepingQueue.remove( 0 );
        }

        // Okay, should be safe now, restore interrupts
        Machine.interrupt().restore(intStatus);
    }

    /**
     * Wake up all threads sleeping on this condition variable. The current
     * thread must hold the associated lock.
     */
    public void wakeAll() {
	Lib.assertTrue(conditionLock.isHeldByCurrentThread());

        // Don't want to be interrupted while setting something important
        boolean intStatus = Machine.interrupt().disable();

        // Until there are no threads sleeping
        while ( sleepingQueue.size() > 0 ) {
            Lib.debug(dbgCondition, "Waking Multiple Threads:");
            // Wake up the thread
            wake();
        }

        // Okay, should be safe now, restore interrupts
        Machine.interrupt().restore(intStatus);

    }

    /**
     * Tests whether this module is working.
     */
    public static void selfTest() {
        Condition2Test.runTest();
    }

    private static final char dbgCondition = 'c';

    private Lock conditionLock;

    private static ArrayList<KThread> sleepingQueue = new ArrayList<KThread>();
}
